/*
 * Copyright (C) 2016 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3.internal.http;

import java.io.IOException;
import java.net.ProtocolException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.Util;
import okhttp3.internal.connection.RealConnection;
import okhttp3.internal.connection.StreamAllocation;
import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;

/**负责从服务器读取响应的数据
 * 这是链中的最后一个拦截器。 它会对服务器进行网络请求
我们通过ConnectInterceptor已经连接到服务器了，接下来我们就是写入请求数据以及读出返回数据了。整个流程：
1、获取request、httpCodec等对象。
2、通过httpCodec发送状态行和头部数据到服务端。
3、判断是否是有请求实体的请求，如果是，判断当前是否有设置Expect: 100-continue请求头。
4、往流中写入请求实体内容。
5、读取服务器返回的头部信息、状态码等，构建responseBuilder对象并返回。
6、通过responseBuilder对象来构建response对象。
7、判断当前状态码，如果是100，则重新请求一次，得到response对象。
8、给Response对象设置body。
9、判断是否需要断开连接。
 * This is the last interceptor in the chain. It makes a network call to the server.
 * @author chenli
 *
 */
public final class CallServerInterceptor implements Interceptor {
	private final boolean forWebSocket;

	public CallServerInterceptor(boolean forWebSocket) {
		this.forWebSocket = forWebSocket;
	}

	@Override
	public Response intercept(Chain chain) throws IOException {
		// 这些对象在前面的Interceptor都已经创建完毕
		RealInterceptorChain realChain = (RealInterceptorChain) chain;
		HttpCodec httpCodec = realChain.httpStream();
		StreamAllocation streamAllocation = realChain.streamAllocation();
		RealConnection connection = (RealConnection) realChain.connection();
		Request request = realChain.request();

		long sentRequestMillis = System.currentTimeMillis();
		realChain.eventListener().requestHeadersStart(realChain.call());
		// 向服务器中写入请求头
		httpCodec.writeRequestHeaders(request);
		realChain.eventListener().requestHeadersEnd(realChain.call(), request);

		Response.Builder responseBuilder = null;
		// 判断是否有请求实体的请求，通过判断请求方式（比如get请求是不需要请求体的，那么就不需要发送请求体）和请求体是否为空来判断
		if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
			// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 Continue" response before transmitting the request body.
			// If we don't get that, return what we did get (such as a 4xx response) without ever transmitting the request body.
			// 如果头部信息添加了"Expect: 100-continue"，这个请求头字段的作用是在发送RequestBody前向服务器确认是否接受RequestBody，
			// 如果服务器不接受也就没有发送的必要了。
			// 有这个字段，相当于一次简单的握手操作，会等待拿到服务器返回的ResponseHeaders之后再继续，如果服务器接收RequestBody，会返回null
			if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
				// 在符合上面条件的情况下，再次判断请求头中是否有Expect：100-continue，如果有，那么就获取到请求头的响应状态码，判断服务端是否想要接收请求体。
				// 如果服务端返回的状态码是100，表示接收请求体，那么就通过readResponseHeaders()方法返回的响应体responseBuilder为空。为什么返回空呢？
				// 在后面的代码中是通过responseBuilder是否为空来判断是否需要发送请求体的
				httpCodec.flushRequest();
				realChain.eventListener().responseHeadersStart(realChain.call());
				// 读取服务端返回的响应
				// readResponseHeaders(true)这个方法里面做的操作如下：
				// 1、读取服务器返回的响应头
				// 2、根据响应码和传入的参数判断返回的respone.builer是否为空
				// (当传入的参数为true(这个可以理解为客户端希望继续发送请求体)并且服务端返回的状态码也是希望我们继续传入请求体的时候，就返回null)
				responseBuilder = httpCodec.readResponseHeaders(true);
			}
			// 如果responseBuilder为空，说明可以继续传入请求体(服务器同意接收，开始向流中写入RequestBody)
			if (responseBuilder == null) {
				// Write the request body if the "Expect: 100-continue" expectation was met.
				realChain.eventListener().requestBodyStart(realChain.call());
				long contentLength = request.body().contentLength();
				// 下面两句代码可以理解为获取到向服务端发送请求体的输出流
				CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, contentLength));
				BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
				// 开始向服务端写入请求体
				request.body().writeTo(bufferedRequestBody);
				bufferedRequestBody.close();
				realChain.eventListener().requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
			} else if (!connection.isMultiplexed()) {
				// 这里的else if表示不需要传入请求体，那么这次请求就算是结束了，如果不是http2协议的话，这次连接就可以关闭掉了
				// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from being reused.
				// Otherwise we're still obligated to transmit the request body to leave the connection in a consistent state.
				streamAllocation.noNewStreams();
			}
		}

		// 执行到这里，不管有没有发送请求体，开始往流里写入数据
		httpCodec.finishRequest();

		if (responseBuilder == null) {
			realChain.eventListener().responseHeadersStart(realChain.call());
			// 有请求体的情况下获取responseBuilder
			responseBuilder = httpCodec.readResponseHeaders(false);
		}

		// 封装respone响应 写入原请求,握手情况,请求时间,得到的结果时间
		Response response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake())
				// 记录发送时间
				.sentRequestAtMillis(sentRequestMillis)
				// 记录接收时间
				.receivedResponseAtMillis(System.currentTimeMillis()).build();
		// 上面的代码主要是通过httpCodec.readResponseHeaders(false)获取到response,这时的response并不包含请求体。然后再根据响应码来做不同的处理

		// 读取响应体
		int code = response.code();
		if (code == 100) {
			// 如果响应码是100，重新读取一遍响应头
			// server sent a 100-continue even though we did not request one. try again to read the actual response
			responseBuilder = httpCodec.readResponseHeaders(false);

			response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake())
					.sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis())
					.build();

			code = response.code();
		}

		realChain.eventListener().responseHeadersEnd(realChain.call(), response);

		// code=101 Switching Protocols 服务器将遵从客户的请求转换到另外一种协议
		if (forWebSocket && code == 101) {
			// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
			response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();
		} else {
			// 这里才为response附上了服务器返回的body
			response = response.newBuilder().body(httpCodec.openResponseBody(response)).build();
		}

		// 根据connection值关闭连接
		if ("close".equalsIgnoreCase(response.request().header("Connection"))
				|| "close".equalsIgnoreCase(response.header("Connection"))) {
			streamAllocation.noNewStreams();
		}

		// 204 - No Content没有新文档，浏览器应该继续显示原来的文档。如果用户定期地刷新页面，而Servlet可以确定用户文档足够新，这个状态代码是很有用的
		// 205 - Reset Content 没有新的内容，但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容（HTTP 1.1新）
		if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
			throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
		}

		return response;
	}

	static final class CountingSink extends ForwardingSink {
		long successfulCount;

		CountingSink(Sink delegate) {
			super(delegate);
		}

		@Override
		public void write(Buffer source, long byteCount) throws IOException {
			super.write(source, byteCount);
			successfulCount += byteCount;
		}
	}
}
